﻿/* Copyright 2008 dnAnalytics Project.
 *
 * Contributors to this file:
 * Marcus Cuda
 *
 * This file is part of dnAnalytics.  dnAnalytics is licensed under the 
 * Microsoft Public License. See License.txt for a complete copy of the
 * license.
 */

using System;
using System.Collections.Generic;
using System.IO;
using dnAnalytics.LinearAlgebra.IO.Matlab;
using dnAnalytics.Properties;

namespace dnAnalytics.LinearAlgebra.IO
{
    /// <summary>
    /// Creates matrices from Matlab files.
    /// </summary>
    public class MatlabMatrixReader : IMatrixReader
    {
        private readonly string mFilename;
        private readonly Stream mStream;

        /// <summary>
        /// Initializes a new instance of the <see cref="MatlabMatrixReader"/> class.
        /// </summary>
        /// <param name="filename">Name of the file to read matrices from.</param>
        public MatlabMatrixReader(string filename)
        {
            if (string.IsNullOrEmpty(filename))
            {
                throw new ArgumentException(Resources.EmptyOrNull, "filename");
            }

            if (!File.Exists(filename))
            {
                throw new FileNotFoundException(Resources.FileDoesNotExist, "filename");
            }
            mFilename = filename;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="MatlabMatrixReader"/> class.
        /// </summary>
        /// <param name="stream">The stream to reader matrices from.</param>
        public MatlabMatrixReader(Stream stream)
        {
            if (stream == null)
            {
                throw new ArgumentNullException("stream");
            }
            mStream = stream;
        }

        #region IMatrixReader Members

        /// <summary>
        /// Reads the first matrix from the file or stream.
        /// </summary>
        /// <returns>
        /// If the matrix is stored as a sparse matrix, then a <see cref="SparseMatrix"/> is returned. Otherwise, a <see cref="DenseMatrix"/>
        /// is returned.
        /// </returns>
        public Matrix ReadMatrix()
        {
            return ReadMatrix(null);
        }

        /// <summary>
        /// Reads the first matrix from the file or stream and returns it with the requested <see cref="StorageType"/>.
        /// </summary>
        /// <param name="storageType">The <see cref="StorageType"/> for the new matrix.</param>
        /// <returns>
        /// If the matrix is stored as a sparse matrix, then a <see cref="SparseMatrix"/> is returned. Otherwise, a <see cref="DenseMatrix"/>
        /// is returned.
        /// </returns>
        public Matrix ReadMatrix(StorageType storageType)
        {
            return ReadMatrix(null, storageType);
        }

        /// <summary>
        /// Reads the named matrix from the file or stream.
        /// </summary>
        /// <param name="matrixName">The name of the matrix to read.</param>
        /// <returns>
        /// If the matrix is stored as a sparse matrix, then a <see cref="SparseMatrix"/> is returned. Otherwise, a <see cref="DenseMatrix"/>
        /// is returned. <see langword="null"/> is returned if a matrix with the requests name doesn't exist.
        /// </returns>
        public Matrix ReadMatrix(string matrixName)
        {
            Stream stream;
            if (mFilename == null)
            {
                stream = mStream;
                mStream.Seek(0, SeekOrigin.Begin);
            }
            else
            {
                stream = new BufferedStream(new FileStream(mFilename, FileMode.Open, FileAccess.Read));
            }
            string[] names = string.IsNullOrEmpty(matrixName) ? new string[] {} : new[] {matrixName};
            MatlabParser parser = new MatlabParser(stream, names);
            MatlabFile file = parser.Parse();

            Matrix matrix = null;
            if (string.IsNullOrEmpty(matrixName))
            {
                matrix = file.FirstMatrix;
            }
            else if (file.Matrices.ContainsKey(matrixName))
            {
                matrix = file.Matrices[matrixName];
            }
            if (mFilename != null)
            {
                stream.Close();
                stream.Dispose();
            }
            return matrix;
        }

        /// <summary>
        /// Reads the named matrix from the file or stream and returns it with the requested <see cref="StorageType"/>.
        /// </summary>
        /// <param name="matrixName">The name of the matrix to read.</param>
        /// <param name="storageType">The <see cref="StorageType"/> for the new matrix.</param>
        /// <returns>
        /// If the matrix is stored as a sparse matrix, then a <see cref="SparseMatrix"/> is returned. Otherwise, a <see cref="DenseMatrix"/>
        /// is returned. <see langword="null"/> is returned if a matrix with the requests name doesn't exist.
        /// </returns>
        public Matrix ReadMatrix(string matrixName, StorageType storageType)
        {
            Matrix matrix = ReadMatrix(matrixName);

            if (matrix != null)
            {
                matrix = GetCorrectMatrixType(matrix, storageType);
            }

            return matrix;
        }


        /// <summary>
        /// Reads all matrices from the file or stream.
        /// </summary>
        /// <returns>All matrices from the file or stream.</returns>
        public Matrix[] ReadMatrices()
        {
            return ReadMatrices(new string[] {});
        }

        /// <summary>
        /// Reads all matrices from the file or stream and returns them with the requested <see cref="StorageType"/>
        /// </summary>
        /// <param name="storageType">The <see cref="StorageType"/> for the new matrices.</param>
        /// <returns>All matrices from the file or stream.</returns>
        public Matrix[] ReadMatrices(StorageType storageType)
        {
            return ReadMatrices(new string[] {}, storageType);
        }

        /// <summary>
        /// Reads the named matrices from the file or stream.
        /// </summary>
        /// <param name="names">The names of the matrices to retrieve.</param>
        /// <returns>
        /// The named matrices from the file or stream.
        /// </returns>
        public Matrix[] ReadMatrices(IEnumerable<string> names)
        {
            Stream stream;
            if (mFilename == null)
            {
                stream = mStream;
                mStream.Seek(0, SeekOrigin.Begin);
            }
            else
            {
                stream = new BufferedStream(new FileStream(mFilename, FileMode.Open, FileAccess.Read));
            }

            MatlabParser parser = new MatlabParser(stream, names);
            MatlabFile file = parser.Parse();

            var matrices = new Matrix[file.Matrices.Count];
            int i = 0;
            foreach (Matrix matrix in file.Matrices.Values)
            {
                matrices[i++] = matrix;
            }
            if (mFilename != null)
            {
                stream.Close();
                stream.Dispose();
            }
            return matrices;
        }

        /// <summary>
        /// Reads the named matrices from the file or stream and returns them with the requested <see cref="StorageType"/>.
        /// </summary>
        /// <param name="names">The names of the matrices to retrieve.</param>
        /// <param name="storageType">The <see cref="StorageType"/> for the new matrices.</param>
        /// <returns>
        /// The named matrices from the file or stream.
        /// </returns>
        public Matrix[] ReadMatrices(IEnumerable<string> names, StorageType storageType)
        {
            Matrix[] matrices = ReadMatrices(names);
            for (int i = 0; i < matrices.Length; i++)
            {
                matrices[i] = GetCorrectMatrixType(matrices[i], storageType);
            }
            return matrices;
        }

        #endregion

        private static Matrix GetCorrectMatrixType(Matrix matrix, StorageType storageType)
        {
            Matrix ret = null;
            if (matrix is DenseMatrix && storageType == StorageType.Dense)
            {
                ret = matrix;
            }
            if (matrix is SparseMatrix && storageType == StorageType.Sparse)
            {
                ret = matrix;
            }
            switch (storageType)
            {
                case StorageType.Dense:
                    ret = new DenseMatrix(matrix);
                    break;
                case StorageType.Sparse:
                    ret = new SparseMatrix(matrix);
                    break;
            }
            return ret;
        }
    }
}